package crawler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Downloader загружает страницы, собирает с них ссылки и хранит очередь
 * непосещенных ссылок, а так же очередь загруженных, но не обработанных страниц
 * 
 * @author Созинов Кирилл
 * @param thread поток, в котором выполняется данный downloader
 * @param runnedThreadsCount количество выполняемых в данный момент потоков
 * @param queue очередь гиперссылок на загрузку.
 * @param baseURL корневой URL сайта
 * @param downloaded очередь загруженных веб-страниц, готовых к обработке
 */

public class Downloader implements Runnable {
    
    /**
     * Необходимость в данном классе возникла из-за того, что кроме самого кода страницы,
     * в анализатор необходимо передавать так же и заголовки страницы, а так же ее статус
     **/
    public class UnparsedPage{
        /**url страницы**/
        public String url;
        /**Код страницы**/
        public String code;
        /**код http статуса**/
        public Integer stateCode; 
        /**строка http статуса**/
        public String stateString;
        /**Удаленность от корневой страницы**/
        public Integer distance;
    }

    public static boolean MEGAFLAG = false;

    /**Поток, в котором запущен текущий экземпляр**/
    private Thread thread;
    /**Количество выполняющихся в текущий момент экземпляров**/
    private static Integer runnedThreadsCount = 0;
    /**Флаг завершения потоков**/
    public static volatile boolean stopped = false;
    /**Базовый URL**/
    public static String baseURL;
    /**Базовый домен**/
    public static String baseDomain;
    /**Очередь, в которой хранятся ссылки для загрузки. 
     * Доступ к этому полю должен осуществлятся только при помощи синхронизированного метода nextHref**/
    private static LinkedHashSet<HLink> queue = new LinkedHashSet<HLink>();
    /**Очередь, в которой хранятся строки с кодом уже загруженных страниц
    * Доступ к этому полю должен осуществлятся только при помощи синхронизированного метода getDownloaded**/
    public static ArrayList<UnparsedPage> downloaded = new ArrayList<UnparsedPage>();
    /**Набор, который запоминает какие страницы мы скачивали**/
    private static HashSet<String> alreadyDownloaded = new HashSet<String>();
    
    
    /**
     * Конструктор по URL корневой страницы
     * @param baseURL URL корневой страницы сайта
     */
    Downloader(String baseURL){
        // надо убирать последний слэш из baseURL, иначе Analyzer будет добавлять относительные адреса с 2мя слэшами
        if(baseURL.charAt(baseURL.length()-1) == '/')
            baseURL = baseURL.substring(0,baseURL.length()-1);
        baseDomain = baseURL.substring(7);
        if(baseDomain.substring(0,4).equals("www."))
            baseDomain = baseDomain.substring(4);

        thread = new Thread(this);
        thread.start();
        thread.setName("Downloader-"+runnedThreadsCount);
        Downloader.baseURL = baseURL;
        addToQueue(baseURL,0);
        runnedThreadsCount++;
        System.out.println("Поток Downloader создан. Сейчас их создано: "+runnedThreadsCount);
    }
    
    /**
     * Конструктор для создания еще одного Downloader-а в дополнительном потоке
     * @throws IllegalStateException если данный конструктор был вызван до Downloader(String baseURL);
     */
    Downloader() throws IllegalStateException{
        if (baseURL==null) throw new IllegalStateException("Нельзя вызывать конструктор Downloader(), пока не выл вызван Downloader(String baseURL)");
        thread = new Thread(this);
        thread.setName("Downloader-"+runnedThreadsCount);
        runnedThreadsCount++;
        System.out.println("Поток Downloader.thread :"+thread.getName());
        System.out.println("Поток Downloader ("+Thread.currentThread().getName()+") создан. Сейчас их создано: "+runnedThreadsCount);
    }
   
    /**
     * Код, который начинает выполняться при вызове this.thread.start()<br>
     * Этот код выполняется в отдельном потоке.
     */
    @Override
    public void run(){       
        System.out.println("Downloader.run работает в потоке: "+Thread.currentThread().getName());
        while (!stopped) {
            //Если очередь пуста, то отправляем поток в ожидание
            synchronized(queue) {
                MEGAFLAG = false;
                if (queue.isEmpty()){
                    try {
                        queue.wait(2000);
                        System.out.println("Очередь на обработку пуста. Поток "+Thread.currentThread().getName()+" ожидает: вызова notify на объекте queue / попробует опросить очередь через две секунды");
                    } catch (InterruptedException ex) {
                        System.err.println("Не удалось приостановить поток "+Thread.currentThread().getName());
                    }
                } else {
                    //System.out.println(nextHref()+" обработана "+Thread.currentThread().getName());
                    MEGAFLAG = true;
                    UnparsedPage up = download(nextHref());   
                    synchronized(downloaded){
                        downloaded.add(up);
                        downloaded.notify();
                    }
                }
            }
        }
        runnedThreadsCount--;
        System.out.println("Поток Downloader ("+Thread.currentThread().getName()+") завершился. Осталось: "+runnedThreadsCount);
    }
    
    /**
     * Получает ссылку и загружает контент, расположенный по данному адресу
     * @param hlink гиперссылка
     * @return код загруженной веб-страницы или null, если произошла ошибка
     */
    private UnparsedPage download(HLink hlink){
        UnparsedPage result = new UnparsedPage();
        result.url=hlink.url.toString();
        result.distance=hlink.distance;
        try {
            URLConnection uc = hlink.url.openConnection();
            //настройка соединения
            uc.setConnectTimeout(Options.getConnectionTimeout());
            uc.setRequestProperty("user-agent", Options.getUserAgent());
            //получение заголовков
            //result.ct=uc.getHeaderField("Content-type");
            result.stateCode=((HttpURLConnection)uc).getResponseCode();
            result.stateString=((HttpURLConnection)uc).getResponseMessage();
            
            //
            InputStreamReader isr = new InputStreamReader(uc.getInputStream());
            BufferedReader br = new BufferedReader(isr);
            String s;
            while((s = br.readLine())!=null){
                result.code+=s;
            }
            br.close();
        } catch (IOException ex) {
            System.err.println("Downloader.download() : IO Exception occured : "+hlink.url);
        }
        if (result.code==null) result.code="";
        if (result.stateString==null) result.stateString="ERROR";
        return result;
    }
    
    /**
     * Возвращает ссылку на поток, в котором выполняется данный downloader
     * @return Thread
     */    
    public Thread getThread(){
        return thread;
    }
    
    /**
     * Возвращает количество элементов в очереди на загрузку
     * @return число элементов, находящихся в очереди на загрузку
     */
    public static Integer getQueueSize(){
        return queue.size();
    }
    
    /**
     * Добавляет адрес веб-страницы в очередь на загрузку.
     * @param strLookLikeURI Адрес, который необходимо добавить в очередь
     * @param distance Удаленность данного URI от текущей страницы
     */
    public static boolean addToQueue(String url, Integer distance){
        //System.out.println("Пытаюсь добавить в очередь: "+strLookLikeURL+" в потоке "+Thread.currentThread().getName()+"\t");
        //попробуем создать URL из полученной строки
        try{
            HLink href = new HLink(new URL(url),distance);
            synchronized (queue){
                if(!alreadyDownloaded.contains(url)) {
                    queue.add(href);
                    alreadyDownloaded.add(url);
                }
                queue.notify();
            }
            return true;
        }
        catch (MalformedURLException murlex){
            System.err.println("Не удалось добавить URL: "+url);
            return false;
        }
    }    
  
    /**
     * Забирает следующую гиперссылку из массива
     * @return HLink - гиперссылка на веб-страницу
     */
    private HLink nextHref() {
        synchronized (queue){
            HLink t = (HLink)queue.toArray()[0];
            queue.remove(t);
            return t;
        }
    }
    
    /**
     * Возвращает информацию о наиболее давно загруженной страницы
     * или null, если страниц в очереди нет
     * @return структура содержащая код веб-страницы и информацию о заголовках и статусе
     */
    public static UnparsedPage getDownloaded() {
        synchronized(downloaded){
            if(!downloaded.isEmpty()){
                UnparsedPage unparsedpage = downloaded.get(0);
                downloaded.remove(0);
                return unparsedpage ;
            } else 
                return null;
                
        }
    }
    
    public static Integer getDownloadedSize(){
        synchronized(downloaded){
            return downloaded.size();
        }
    }
    
    public static Integer getTIC(String adr){
        try {
            URL url = new URL("http://bar-navig.yandex.ru/u?ver=2&show=32&url="+adr);
            URLConnection uc = url.openConnection();
            //настройка соединения
            uc.setConnectTimeout(Options.getConnectionTimeout());
            uc.setRequestProperty("user-agent", Options.getUserAgent());
            //
            InputStreamReader isr = new InputStreamReader(uc.getInputStream());
            BufferedReader br = new BufferedReader(isr);
            String s;
            String result = new String();
            while((s = br.readLine())!=null){
                result+=s;
            }
            br.close();
            System.err.println(result);
            Matcher matcher = Pattern.compile("tcy rang=\"(\\d+)\"").matcher(result);
            if (matcher.find()) {
                return new Integer(matcher.group(1));
            }
            else return -1;
        } catch (IOException ex) {
            System.err.println("Downloader.getTIC() : IO Exception occured : ");
        }
        return 0;
    }
    
    public static ArrayList<String> getRobotsTxt(String adr){
        try {
            ArrayList<String> res = new ArrayList<String>();
            URL url = new URL(adr+"/robots.txt");
            URLConnection uc = url.openConnection();
            //настройка соединения
            uc.setConnectTimeout(Options.getConnectionTimeout());
            //
            InputStreamReader isr = new InputStreamReader(uc.getInputStream());
            BufferedReader br = new BufferedReader(isr);
            String s;
            while((s = br.readLine())!=null){
                res.add(s);
            }
            for (String string : res) {
                System.err.println(string);
            }
            br.close();
            return res;
        } catch (IOException ex) {
            System.err.println("Downloader.getRobotsTxt : IO Exception occured : ");
            return null;
        }
    }

}
